﻿using System;
using System.Collections;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental
{
    /// <summary>
    /// Boolean vector model in Boolean Mathematics.
    /// </summary>
    public class BooleanVector : IEnumerable, ICloneable, IComparable
    {
        // Fields

        /// <summary>
        /// Vertor data.
        /// </summary>
        protected BitArray BitData;

        /// <summary>
        /// Additional vector data.
        /// </summary>
        public object Data;

        /// <summary>
        /// Additional vector data #2.
        /// </summary>
        public object Data2;

        /// <summary>
        /// Additional vector data #3.
        /// </summary>
        public object Data3;


        // Methods

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="previousVector">Object to copy from.</param>
        public BooleanVector(BooleanVector previousVector)
        {
            BitData = new BitArray(previousVector.BitData);
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public BooleanVector()
        {
            BitData = new BitArray(0);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="bits">BitArray to copy.</param>
        public BooleanVector(BitArray bits)
        {
            BitData = new BitArray(bits);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="length">Length of the vector.</param>
        public BooleanVector(int length)
        {
            BitData = new BitArray(length);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="values">String values '0'/'1' to convert to boolean bits.</param>
        public BooleanVector(string values)
        {
            BitData = new BitArray(values.Length);

            // filling vector
            for (var i = 0; i < values.Length; i++)
            {
                if (values[i] == '0')
                {
                    BitData.Set(i, false);
                }
                else if (values[i] == '1')
                {
                    BitData.Set(i, true);
                }
                else
                {
                    throw new ArgumentException(
                        "Values string for boolean vector must contains of '0' and '1' characters only.");
                }
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="values">An array of boolean values.</param>
        public BooleanVector(bool[] values)
        {
            BitData = new BitArray(values);
        }


        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="values">An array of 32-bit integer values.</param>
        public BooleanVector(int[] values)
        {
            BitData = new BitArray(values);
        }

        /// <summary>
        /// Length of the boolean vector.
        /// </summary>
        public int Length
        {
            get { return BitData.Length; }
            set { BitData.Length = value; }
        }

        /// <summary>
        /// Vector's weight (number of 1 bits).
        /// </summary>
        public int Weight
        {
            get
            {
                return BitData.Cast<bool>().Count(value => value);
            }
        }

        /// <summary>
        /// Null vector indicator.
        /// </summary>
        public bool IsNull
        {
            get
            { return BitData.Cast<bool>().All(value => !value); }
        }

        /// <summary>
        /// Checkes whether vectors are orthogonal.
        /// </summary>
        /// <param name="vector">Vector to compare with.</param>
        /// <returns>True if vectors are orthgonal.</returns>
        public bool IsOrthogonalTo(BooleanVector vector)
        {
            if (Length != vector.Length)
            {
                throw new InvalidOperationException("Vectors aren't compatible: different lengthes.");
            }

            for (var i = 0; i < Length; i++)
            {
                if (this[i] != vector[i])
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Boolean vector indexer.
        /// </summary>
        /// <param name="index">Bit index.</param>
        /// <returns>Vector bit by index.</returns>
        public BooleanValue this[int index]
        {
            get
            {
                try
                {
                    return BitData[index] ? BooleanValue.True : BooleanValue.False;
                }
                catch (IndexOutOfRangeException)
                {
                    throw new IndexOutOfRangeException("Invalid vector index.");
                }
            }

            set
            {
                if (value != BooleanValue.True &&
                    value != BooleanValue.False)
                {
                    throw new ArgumentException("Invalid boolean value");
                }

                try
                {
                    BitData[index] = (value == BooleanValue.True);
                }
                catch (IndexOutOfRangeException)
                {
                    throw new IndexOutOfRangeException("Invalid vector index.");
                }
            }
        }

        #region ICloneable Members

        /// <summary>
        /// Clones boolean vector object.
        /// </summary>
        /// <returns>Object's clone.</returns>
        public Object Clone()
        {
            var clone = new BooleanVector(this);
            return clone;
        }

        #endregion

        #region IComparable Members

        /// <summary>
        /// Compares boolean vector with arbitrary object.
        /// </summary>
        /// <param name="obj">Object to compare with.</param>
        /// <returns>0 if objects are equal, less than zero if current object less than another one or greater than zero if current object greater than another one.</returns>
        public int CompareTo(Object obj)
        {
            if (obj is BooleanVector)
            {
                var v = obj as BooleanVector;

                // comparing boolean vectors
                if (Length != v.Length)
                {
                    throw new InvalidOperationException("Vectors aren't comparable (different lengthes).");
                }

                for (var i = 0; i < Length; i++)
                {
                    if (this[i] == v[i]) continue;
                    if (this[i] == BooleanValue.False)
                    {
                        return -1;
                    }
                    return 1;
                }
            }

            return Int32.MinValue;
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Returns enumerator.
        /// </summary>
        /// <returns>Vector's enumerator.</returns>
        public IEnumerator GetEnumerator()
        {
            return BitData.Cast<object>().Select((t, i) => this[i]).GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Determines whether specified object is equal to the current Object.
        /// </summary>
        /// <param name="obj">The Object to compare with the current Object.</param>
        /// <returns>True if the specified Object equals to current Object.</returns>
        public override bool Equals(object obj)
        {
            if (obj is BooleanVector)
            {
                return Equals(obj as BooleanVector);
            }
            
            return Equals(this, obj);
        }

        /// <summary>
        /// Transforms boolean vector to string representation.
        /// </summary>
        /// <returns>String representation.</returns>
        public override string ToString()
        {
            var values = String.Empty;

            foreach (BooleanValue value in this)
            {
                if (value == BooleanValue.True)
                {
                    values += '1';
                }
                else
                {
                    values += '0';
                }
            }

            return values;
        }

        /// <summary>
        /// Implicit conversion from string to BooleanVectror.
        /// </summary>
        /// <param name="values">String values of boolean vector.</param>
        /// <returns>BooleanVector constructed from string values.</returns>
        public static implicit operator BooleanVector(string values)
        {
            return new BooleanVector(values);
        }

        /// <summary>
        /// Implicit conversion from BooleanVector object to string.
        /// </summary>
        /// <param name="vector">BooleanVector object.</param>
        /// <returns>String representation of the boolean vector.</returns>
        public static implicit operator string(BooleanVector vector)
        {
            return vector.ToString();
        }

        /// <summary>
        /// Inverts all bits in boolean vector.
        /// </summary>
        /// <param name="vector">Input boolean vector to copy from.</param>
        /// <returns>Inverted boolean vector.</returns>
        public static BooleanVector operator ~(BooleanVector vector)
        {
            var result = new BooleanVector(vector.BitData.Not());
            return result;
        }

        /// <summary>
        /// Performs logical AND operation with two BooleanVector objects.
        /// </summary>
        /// <param name="v1">First vector.</param>
        /// <param name="v2">Second vector.</param>
        /// <returns>Operation result.</returns>
        public static BooleanVector operator &(BooleanVector v1, BooleanVector v2)
        {
            if (v1.Length != v2.Length)
            {
                // different lengthes
                throw new ArgumentException("Vectors must have the same length.");
            }

            var v1Data = new BitArray(v1.BitData);
            var v2Data = new BitArray(v2.BitData);
            var dataResult = v1Data.And(v2Data);
            return new BooleanVector(dataResult);
        }


        /// <summary>
        /// Performs logical OR operation with two BooleanVector objects.
        /// </summary>
        /// <param name="v1">First vector.</param>
        /// <param name="v2">Second vector.</param>
        /// <returns>Operation result.</returns>
        public static BooleanVector operator |(BooleanVector v1, BooleanVector v2)
        {
            if (v1.Length != v2.Length)
            {
                // different lengthes
                throw new ArgumentException("Vectors must have the same length.");
            }

            var v1Data = new BitArray(v1.BitData);
            var v2Data = new BitArray(v2.BitData);
            var dataResult = v1Data.Or(v2Data);
            return new BooleanVector(dataResult);
        }


        /// <summary>
        /// Performs logical XOR operation with two BooleanVector objects.
        /// </summary>
        /// <param name="v1">First vector.</param>
        /// <param name="v2">Second vector.</param>
        /// <returns>Operation result.</returns>
        public static BooleanVector operator ^(BooleanVector v1, BooleanVector v2)
        {
            if (v1.Length != v2.Length)
            {
                // different lengthes
                throw new ArgumentException("Vectors must have the same length.");
            }

            var v1Data = new BitArray(v1.BitData);
            var v2Data = new BitArray(v2.BitData);
            var dataResult = v1Data.Xor(v2Data);
            return new BooleanVector(dataResult);
        }

        /// <summary>
        /// Performs left cycle shift of vector bits.
        /// </summary>
        /// <param name="vector">BooleanVector object.</param>
        /// <param name="shift">Shift length.</param>
        /// <returns>Shifted vector.</returns>
        public static BooleanVector operator <<(BooleanVector vector, int shift)
        {
            var data = (BitArray) vector.BitData.Clone();

            // shifting bits
            while (shift > 0)
            {
                var cycledBit = data[0];
                for (var i = 0; i < data.Length - 1; i++)
                {
                    data[i] = data[i + 1];
                }
                data[data.Length - 1] = cycledBit;

                shift--;
            }

            var result = new BooleanVector(data);
            return result;
        }


        /// <summary>
        /// Performs right cycle shift of vector bits.
        /// </summary>
        /// <param name="vector">BooleanVector object.</param>
        /// <param name="shift">Shift length.</param>
        /// <returns>Shifted vector.</returns>
        public static BooleanVector operator >>(BooleanVector vector, int shift)
        {
            var data = (BitArray) vector.BitData.Clone();

            // shifting bits
            while (shift > 0)
            {
                var cycledBit = data[data.Length - 1];
                for (var i = data.Length - 1; i > 0; i--)
                {
                    data[i] = data[i - 1];
                }
                data[0] = cycledBit;

                shift--;
            }

            var result = new BooleanVector(data);
            return result;
        }

        /// <summary>
        /// Checks whether boolean vectors values are equal.
        /// </summary>
        /// <param name="obj">Boolean vector instance.</param>
        /// <returns>True if vectors are equal.</returns>
        public bool Equals(BooleanVector obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;

            if (Length != obj.Length)
            {
                return false;
            }

            // checking bits
            for (var i = 0; i < Length; i++)
            {
                if (this[i] != obj[i])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Checks value equality of two boolean vectors.
        /// </summary>
        /// <param name="vector1">First boolean vector.</param>
        /// <param name="vector2">Second boolean vector.</param>
        /// <returns>True if boolean values are equal.</returns>
        public static bool operator ==(BooleanVector vector1, BooleanVector vector2)
        {
            if (ReferenceEquals(vector1, vector2))
            {
                return true;
            }

            if ((object)vector1 == null || (object)vector2 == null)
            {
                return false;
            }

            return vector1.Equals(vector2);
        }

        /// <summary>
        /// Checks value inequality of two boolean vectors.
        /// </summary>
        /// <param name="vector1">First boolean vector.</param>
        /// <param name="vector2">Second boolean vector.</param>
        /// <returns>True if boolean values are not equal.</returns>
        public static bool operator !=(BooleanVector vector1, BooleanVector vector2)
        {
            return !(vector1 == vector2);
        }

        /// <summary>
        /// Returns hash code for BooleanVector object.
        /// </summary>
        /// <returns>Int hash code.</returns>
        public override int GetHashCode()
        {
            if (ReferenceEquals(BitData, null))
            {
                return 0;
            }

            var hashCode = 0;
            for (var i = Length - 1; i >= 0; i--)
            {
                if (this[i] != BooleanValue.True)
                {
                    continue;
                }

                var power = Length - i - 1;
                hashCode += (int)Math.Pow(2, power);
            }

            return hashCode;
        }

        /// <summary>
        /// Creates vector reverse (first bit will be the last one and so one).
        /// </summary>
        /// <returns>Reversed boolean vector.</returns>
        public BooleanVector Reverse()
        {
            var clone = (BooleanVector) Clone();
            for (var i = 0; i < clone.Length/2; i++)
            {
                var tmp = clone[i];
                clone[i] = clone[clone.Length - i - 1];
                clone[clone.Length - i - 1] = tmp;
            }

            return clone;
        }

        /// <summary>
        /// Builds random boolean vector of specified length.
        /// </summary>
        /// <param name="vectorLength">New vector length.</param>
        /// <returns>Randomized vector.</returns>
        public static BooleanVector Random(int vectorLength)
        {
            var random = new Random();
            var result = new BooleanVector(vectorLength);
            for (var i = 0; i < result.Length; i++)
            {
                result[i] = (Int32)Math.Round(random.NextDouble()) == 0 ? BooleanValue.False : BooleanValue.True;
            }

            return result;
        }

        /// <summary>
        /// Calculates distance between two vectors in terms of Hamming distance (number of different bits).
        /// </summary>
        /// <param name="vector">BooleanVector object.</param>
        /// <returns>Hamming distance.</returns>
        public int Distance(BooleanVector vector)
        {
            if (Length != vector.Length)
            {
                throw new ArgumentException("Vectors aren't compatible (different lengthes).");
            }
            var distance = 0;
            for (var i = 0; i < Length; i++)
            {
                if (this[i] != vector[i])
                {
                    distance++;
                }
            }

            return distance;
        }
    }
}